﻿//////////////////////////////////////////////////////////////////
//
// DashboardRiemers.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using Interfaces;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Utilities;

namespace DashboardXna
{
   public partial class DashboardRiemers : GraphicsDeviceControl
   {
      ////////////////////////////////////////////////////////////////////////
      //
      private Logger logger_;
      private ContentManager content_;
      private Stopwatch sw_;

      int terrainWidth;
      int terrainLength;
      float[,] heightData;

      VertexBuffer terrainVertexBuffer;
      IndexBuffer terrainIndexBuffer;
      VertexMultitextured[] vertices;
      int[] indices;

      Effect effect;
      Effect bbEffect;

      Matrix viewMatrix;
      Matrix projectionMatrix;
      Matrix reflectionViewMatrix;

      Vector3 cameraPosition = new Vector3(130, 30, -50);
      float leftrightRot = MathHelper.PiOver2;
      float updownRot = -MathHelper.Pi / 10.0f;
      const float rotationSpeed = 0.1f;
      const float moveSpeed = 30.0f;
      MouseState originalMouseState;

      Texture2D grassTexture;
      Texture2D sandTexture;
      Texture2D rockTexture;
      Texture2D snowTexture;
      Texture2D cloudMap;
      Texture2D waterBumpMap;
      Texture2D treeTexture;

      VertexBuffer treeVertexBuffer;
      VertexDeclaration treeVertexDeclaration;

      Model skyDome;

      const float waterHeight = 5.0f;
      RenderTarget2D refractionRenderTarget;
      Texture2D refractionMap;

      RenderTarget2D reflectionRenderTarget;
      Texture2D reflectionMap;

      VertexBuffer waterVertexBuffer;

      Vector3 windDirection = new Vector3(1, 0, 0);

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Implementation of IDashboard
      /// </summary>
      public void Tick()
      {
         //if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
         //    this.Exit();

         //float timeDifference = (float)sw_.ElapsedMilliseconds / 1000.0f;
         //ProcessInput(timeDifference);
         //Invalidate();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Allows the game component to perform any initialization it needs to before starting
      /// to run.  This is where it can query for any required services and load content.
      /// </summary>
      protected override void Initialize()
      {
         // DON'T DO THIS IN THE CONSTRUCTOR, IT HASN'T BEEN INITIALIZED UNTIL NOW
         logger_ = Program.Logger;

         content_ = new ContentManager(Services, "DashboardContentXna");
         content_.RootDirectory = "DashboardContentXna";

         LoadContent();
      }


      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// This is called when the game should draw itself.
      /// </summary>
      /// <param name="gameTime">Provides a snapshot of timing values.</param>
      protected override void Draw()
      {
         float time = sw_.ElapsedMilliseconds / 100.0f;
         RasterizerState rs = new RasterizerState();
         rs.CullMode = CullMode.None;
         GraphicsDevice.RasterizerState = rs;

         DrawRefractionMap();
         DrawReflectionMap();

         GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

         DrawSkyDome(viewMatrix);
         DrawTerrain(viewMatrix);
         DrawWater(time);
         DrawBillboards(viewMatrix);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      protected void LoadContent()
      {
         effect = content_.Load<Effect>("dashboardEffects");
         UpdateViewMatrix();

         viewMatrix = Matrix.CreateLookAt(new Vector3(130, 30, -50), new Vector3(0, 0, -40), new Vector3(0, 1, 0));
         projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 0.3f, 1000.0f);

         skyDome = content_.Load<Model>("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone();

         PresentationParameters pp = GraphicsDevice.PresentationParameters;
         refractionRenderTarget = new RenderTarget2D(GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat);

         reflectionRenderTarget = new RenderTarget2D(GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat);

         Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
         originalMouseState = Mouse.GetState();

         bbEffect = content_.Load<Effect>("bbEffect");

         LoadVertices();
         LoadTextures();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      private void LoadVertices()
      {
         Texture2D heightMap = content_.Load<Texture2D>("heightmap"); LoadHeightData(heightMap);
         VertexMultitextured[] terrainVertices = SetUpTerrainVertices();
         int[] terrainIndices = SetUpTerrainIndices();
         terrainVertices = CalculateNormals(terrainVertices, terrainIndices);
         CopyToTerrainBuffers(terrainVertices, terrainIndices);

         List<Vector3> treeList = GenerateTreePositions(terrainVertices);
         CreateBillboardVerticesFromList(treeList);

         SetUpWaterVertices();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      private void LoadTextures()
      {
         grassTexture = content_.Load<Texture2D>("grass");
         sandTexture = content_.Load<Texture2D>("sand");
         rockTexture = content_.Load<Texture2D>("rock");
         snowTexture = content_.Load<Texture2D>("snow");
         treeTexture = content_.Load<Texture2D>("tree");

         cloudMap = content_.Load<Texture2D>("cloudMap");
         waterBumpMap = content_.Load<Texture2D>("waterbump");
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="heightMap"></param>
      private void LoadHeightData(Texture2D heightMap)
      {
         float minimumHeight = float.MaxValue;
         float maximumHeight = float.MinValue;

         terrainWidth = heightMap.Width;
         terrainLength = heightMap.Height;

         Color[] heightMapColors = new Color[terrainWidth * terrainLength];
         heightMap.GetData(heightMapColors);

         heightData = new float[terrainWidth, terrainLength];
         for (int x = 0; x < terrainWidth; x++)
            for (int y = 0; y < terrainLength; y++)
            {
               heightData[x, y] = heightMapColors[x + y * terrainWidth].R;
               if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y];
               if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y];
            }

         for (int x = 0; x < terrainWidth; x++)
            for (int y = 0; y < terrainLength; y++)
               heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * 30.0f;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      private VertexMultitextured[] SetUpTerrainVertices()
      {
         VertexMultitextured[] terrainVertices = new VertexMultitextured[terrainWidth * terrainLength];
         vertices = new VertexMultitextured[terrainWidth * terrainLength];

         for (int x = 0; x < terrainWidth; x++)
         {
            for (int y = 0; y < terrainLength; y++)
            {
               terrainVertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y);
               terrainVertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / 30.0f;
               terrainVertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / 30.0f;

               terrainVertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 0) / 8.0f, 0, 1);
               terrainVertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 12) / 6.0f, 0, 1);
               terrainVertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 20) / 6.0f, 0, 1);
               terrainVertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 30) / 6.0f, 0, 1);


               float total = terrainVertices[x + y * terrainWidth].TexWeights.X;
               total += terrainVertices[x + y * terrainWidth].TexWeights.Y;
               total += terrainVertices[x + y * terrainWidth].TexWeights.Z;
               total += terrainVertices[x + y * terrainWidth].TexWeights.W;

               terrainVertices[x + y * terrainWidth].TexWeights.X /= total;
               terrainVertices[x + y * terrainWidth].TexWeights.Y /= total;
               terrainVertices[x + y * terrainWidth].TexWeights.Z /= total;
               terrainVertices[x + y * terrainWidth].TexWeights.W /= total;
            }
         }
         return terrainVertices;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="terrainVertices"></param>
      /// <returns></returns>
      private List<Vector3> GenerateTreePositions(VertexMultitextured[] terrainVertices)
      {
         List<Vector3> treeList = new List<Vector3>();
         treeList.Add(terrainVertices[3310].Position);
         treeList.Add(terrainVertices[3315].Position);
         treeList.Add(terrainVertices[3320].Position);
         treeList.Add(terrainVertices[3325].Position);

         return treeList;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      private int[] SetUpTerrainIndices()
      {
         int counter = 0;
         indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6];

         for (int y = 0; y < terrainLength - 1; y++)
         {
            for (int x = 0; x < terrainWidth - 1; x++)
            {
               int lowerLeft = x + y * terrainWidth;
               int lowerRight = (x + 1) + y * terrainWidth;
               int topLeft = x + (y + 1) * terrainWidth;
               int topRight = (x + 1) + (y + 1) * terrainWidth;

               indices[counter++] = topLeft;
               indices[counter++] = lowerRight;
               indices[counter++] = lowerLeft;

               indices[counter++] = topLeft;
               indices[counter++] = topRight;
               indices[counter++] = lowerRight;
            }
         }

         return indices;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="vertices"></param>
      /// <param name="indices"></param>
      /// <returns></returns>
      private VertexMultitextured[] CalculateNormals(VertexMultitextured[] vertices, int[] indices)
      {
         for (int i = 0; i < vertices.Length; i++)
            vertices[i].Normal = new Vector3(0, 0, 0);

         for (int i = 0; i < indices.Length / 3; i++)
         {
            int index1 = indices[i * 3];
            int index2 = indices[i * 3 + 1];
            int index3 = indices[i * 3 + 2];

            Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
            Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
            Vector3 normal = Vector3.Cross(side1, side2);

            vertices[index1].Normal += normal;
            vertices[index2].Normal += normal;
            vertices[index3].Normal += normal;
         }

         for (int i = 0; i < vertices.Length; i++)
            vertices[i].Normal.Normalize();

         return vertices;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="vertices"></param>
      /// <param name="indices"></param>
      private void CopyToTerrainBuffers(VertexMultitextured[] vertices, int[] indices)
      {
         VertexDeclaration vertexDeclaration = new VertexDeclaration(VertexMultitextured.VertexElements);

         terrainVertexBuffer = new VertexBuffer(GraphicsDevice, vertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
         terrainVertexBuffer.SetData(vertices.ToArray());

         terrainIndexBuffer = new IndexBuffer(GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);
         terrainIndexBuffer.SetData(indices);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      private void SetUpWaterVertices()
      {
         VertexPositionTexture[] waterVertices = new VertexPositionTexture[6];

         waterVertices[0] = new VertexPositionTexture(new Vector3(0, waterHeight, 0), new Vector2(0, 1));
         waterVertices[2] = new VertexPositionTexture(new Vector3(terrainWidth, waterHeight, -terrainLength), new Vector2(1, 0));
         waterVertices[1] = new VertexPositionTexture(new Vector3(0, waterHeight, -terrainLength), new Vector2(0, 0));

         waterVertices[3] = new VertexPositionTexture(new Vector3(0, waterHeight, 0), new Vector2(0, 1));
         waterVertices[5] = new VertexPositionTexture(new Vector3(terrainWidth, waterHeight, 0), new Vector2(1, 1));
         waterVertices[4] = new VertexPositionTexture(new Vector3(terrainWidth, waterHeight, -terrainLength), new Vector2(1, 0));

         waterVertexBuffer = new VertexBuffer(GraphicsDevice, VertexPositionTexture.VertexDeclaration, waterVertices.Count(), BufferUsage.WriteOnly);
         waterVertexBuffer.SetData(waterVertices);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="treeList"></param>
      private void CreateBillboardVerticesFromList(List<Vector3> treeList)
      {
         VertexPositionTexture[] billboardVertices = new VertexPositionTexture[treeList.Count * 6];
         int i = 0;
         foreach (Vector3 currentV3 in treeList)
         {
            billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(0, 0));
            billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(1, 0));
            billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(1, 1));

            billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(0, 0));
            billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(1, 1));
            billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(0, 1));
         }

         VertexDeclaration vertexDeclaration = VertexPositionTexture.VertexDeclaration;

         treeVertexBuffer = new VertexBuffer(GraphicsDevice, vertexDeclaration, billboardVertices.Length, BufferUsage.WriteOnly);
         treeVertexBuffer.SetData(billboardVertices);
         treeVertexDeclaration = vertexDeclaration;
      }

      private void ProcessInput(float amount)
      {
         MouseState currentMouseState = Mouse.GetState();
         if ((currentMouseState != originalMouseState) && (currentMouseState.RightButton == ButtonState.Pressed))
         {
            float xDifference = currentMouseState.X - originalMouseState.X;
            float yDifference = currentMouseState.Y - originalMouseState.Y;
            leftrightRot -= rotationSpeed * xDifference;// *amount;
            updownRot -= rotationSpeed * yDifference;// * amount;
            Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
            UpdateViewMatrix();
         }

         Vector3 moveVector = new Vector3(0, 0, 0);
         KeyboardState keyState = Keyboard.GetState();
         if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W))
            moveVector += new Vector3(0, 0, -1);
         if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S))
            moveVector += new Vector3(0, 0, 1);
         if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D))
            moveVector += new Vector3(1, 0, 0);
         if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A))
            moveVector += new Vector3(-1, 0, 0);
         if (keyState.IsKeyDown(Keys.Q))
            moveVector += new Vector3(0, 1, 0);
         if (keyState.IsKeyDown(Keys.Z))
            moveVector += new Vector3(0, -1, 0);

         AddToCameraPosition(moveVector * amount);
      }

      private void AddToCameraPosition(Vector3 vectorToAdd)
      {
         Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);
         Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation);
         cameraPosition += moveSpeed * rotatedVector;
         UpdateViewMatrix();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      private void UpdateViewMatrix()
      {
         Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);

         Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
         Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);

         Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
         Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget;

         Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

         viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector);


         Vector3 reflCameraPosition = cameraPosition;
         reflCameraPosition.Y = -cameraPosition.Y + waterHeight * 2;
         Vector3 reflTargetPos = cameraFinalTarget;
         reflTargetPos.Y = -cameraFinalTarget.Y + waterHeight * 2;

         Vector3 cameraRight = Vector3.Transform(new Vector3(1, 0, 0), cameraRotation);
         Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition);

         reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="currentViewMatrix"></param>
      private void DrawTerrain(Matrix currentViewMatrix)
      {
         effect.CurrentTechnique = effect.Techniques["MultiTextured"];
         effect.Parameters["xTexture0"].SetValue(sandTexture);
         effect.Parameters["xTexture1"].SetValue(grassTexture);
         effect.Parameters["xTexture2"].SetValue(rockTexture);
         effect.Parameters["xTexture3"].SetValue(snowTexture);

         Matrix worldMatrix = Matrix.Identity;
         effect.Parameters["xWorld"].SetValue(worldMatrix);
         effect.Parameters["xView"].SetValue(currentViewMatrix);
         effect.Parameters["xProjection"].SetValue(projectionMatrix);

         effect.Parameters["xEnableLighting"].SetValue(true);
         effect.Parameters["xAmbient"].SetValue(0.4f);
         effect.Parameters["xLightDirection"].SetValue(new Vector3(-0.5f, -1, -0.5f));
         foreach (EffectPass pass in effect.CurrentTechnique.Passes)
         {
            pass.Apply();

            GraphicsDevice.Indices = terrainIndexBuffer;
            GraphicsDevice.SetVertexBuffer(terrainVertexBuffer);

            int nV = vertices.Length / 2;
            int nI = indices.Length / 2;

            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nV, 0, nI / 3);
            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, nV, 0, nV, nI, nI / 3);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="currentViewMatrix"></param>
      private void DrawSkyDome(Matrix currentViewMatrix)
      {
         GraphicsDevice.DepthStencilState = DepthStencilState.None;
         Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count];
         skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms);
         Matrix wMatrix = Matrix.CreateTranslation(0, -0.3f, 0) * Matrix.CreateScale(100) * Matrix.CreateTranslation(cameraPosition);
         foreach (ModelMesh mesh in skyDome.Meshes)
         {
            foreach (Effect currentEffect in mesh.Effects)
            {
               Matrix worldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix;
               currentEffect.CurrentTechnique = currentEffect.Techniques["Textured"];
               currentEffect.Parameters["xWorld"].SetValue(worldMatrix);
               currentEffect.Parameters["xView"].SetValue(currentViewMatrix);
               currentEffect.Parameters["xProjection"].SetValue(projectionMatrix);
               currentEffect.Parameters["xTexture"].SetValue(cloudMap);
               currentEffect.Parameters["xEnableLighting"].SetValue(false);
            }
            mesh.Draw();
         }
         GraphicsDevice.BlendState = BlendState.Opaque;
         GraphicsDevice.DepthStencilState = DepthStencilState.Default;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="height"></param>
      /// <param name="planeNormalDirection"></param>
      /// <param name="currentViewMatrix"></param>
      /// <param name="clipSide"></param>
      /// <returns></returns>
      private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide)
      {
         planeNormalDirection.Normalize();
         Vector4 planeCoeffs = new Vector4(planeNormalDirection, height);
         if (clipSide) planeCoeffs *= -1;
         Plane finalPlane = new Plane(planeCoeffs);
         return finalPlane;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      private void DrawRefractionMap()
      {
         Plane refractionPlane = CreatePlane(waterHeight + 1.5f, new Vector3(0, -1, 0), viewMatrix, false);

         effect.Parameters["ClipPlane0"].SetValue(new Vector4(refractionPlane.Normal, refractionPlane.D));
         effect.Parameters["Clipping"].SetValue(true);    // Allows the geometry to be clipped for the purpose of creating a refraction map
         GraphicsDevice.SetRenderTarget(refractionRenderTarget);
         GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);
         DrawTerrain(viewMatrix);
         GraphicsDevice.SetRenderTarget(null);
         effect.Parameters["Clipping"].SetValue(false);   // Make sure you turn it back off so the whole scene doesnt keep rendering as clipped
         refractionMap = refractionRenderTarget;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      private void DrawReflectionMap()
      {
         Plane reflectionPlane = CreatePlane(waterHeight - 0.5f, new Vector3(0, -1, 0), reflectionViewMatrix, true);

         effect.Parameters["ClipPlane0"].SetValue(new Vector4(reflectionPlane.Normal, reflectionPlane.D));
         effect.Parameters["Clipping"].SetValue(true);    // Allows the geometry to be clipped for the purpose of creating a refraction map
         GraphicsDevice.SetRenderTarget(reflectionRenderTarget);
         GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);
         DrawSkyDome(reflectionViewMatrix);
         DrawTerrain(reflectionViewMatrix);

         effect.Parameters["Clipping"].SetValue(false);
         GraphicsDevice.SetRenderTarget(null);
         reflectionMap = reflectionRenderTarget;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="time"></param>
      private void DrawWater(float time)
      {
         effect.CurrentTechnique = effect.Techniques["Water"];
         Matrix worldMatrix = Matrix.Identity;
         effect.Parameters["xWorld"].SetValue(worldMatrix);
         effect.Parameters["xView"].SetValue(viewMatrix);
         effect.Parameters["xReflectionView"].SetValue(reflectionViewMatrix);
         effect.Parameters["xProjection"].SetValue(projectionMatrix);
         effect.Parameters["xReflectionMap"].SetValue(reflectionMap);
         effect.Parameters["xRefractionMap"].SetValue(refractionMap);
         effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap);
         effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap);
         effect.Parameters["xWaveLength"].SetValue(0.1f);
         effect.Parameters["xWaveHeight"].SetValue(0.3f);
         effect.Parameters["xTime"].SetValue(time);
         effect.Parameters["xWindForce"].SetValue(0.002f);
         effect.Parameters["xWindDirection"].SetValue(windDirection);
         effect.CurrentTechnique.Passes[0].Apply();

         GraphicsDevice.SetVertexBuffer(waterVertexBuffer);
         GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, waterVertexBuffer.VertexCount / 3);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="currentViewMatrix"></param>
      private void DrawBillboards(Matrix currentViewMatrix)
      {
         bbEffect.CurrentTechnique = bbEffect.Techniques["CylBillboard"];
         bbEffect.Parameters["xWorld"].SetValue(Matrix.Identity);
         bbEffect.Parameters["xView"].SetValue(currentViewMatrix);
         bbEffect.Parameters["xProjection"].SetValue(projectionMatrix);
         bbEffect.Parameters["xCamPos"].SetValue(cameraPosition);
         bbEffect.Parameters["xAllowedRotDir"].SetValue(new Vector3(0, 1, 0));
         bbEffect.Parameters["xBillboardTexture"].SetValue(treeTexture);

         GraphicsDevice.BlendState = BlendState.AlphaBlend;
         foreach (EffectPass pass in bbEffect.CurrentTechnique.Passes)
         {
            pass.Apply();
            GraphicsDevice.SetVertexBuffer(treeVertexBuffer);
            int noVertices = treeVertexBuffer.VertexCount;
            int noTriangles = noVertices / 3;
            GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, noTriangles);
         }
         GraphicsDevice.BlendState = BlendState.Opaque;
      }
   }
}
